package com.zang.im.croom.netty.handler;

import com.zang.im.croom.netty.processor.WebSocketProcessor;
import com.zang.im.croom.netty.common.SSGlobalConstMap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

import java.net.InetSocketAddress;
import java.util.Map;

import static io.netty.handler.codec.http.HttpHeaderUtil.isKeepAlive;
import static io.netty.handler.codec.http.HttpHeaderUtil.setContentLength;
import static io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse;
/**
 * webSocket
 * @author Zhang Qiang
 * @Date 2019/7/23 16:58
 */
@Slf4j
public class ServerHandler extends SimpleChannelInboundHandler<Object>{
    /**
     * 请求类型常量
     */
    private static final String WEBSOCKET_UPGRADE = "websocket";
    private static final String WEBSOCKET_CONNECTION = "Upgrade";
    private static final String WEBSOCKET_URI_ROOT_PATTERN = "ws://%s:%d";

     /**
      * 服务器端 WebSocket 开关握手的基类
      */
    private WebSocketServerHandshaker handshaker;

     /**
      * 所有链接
      */
    private Map<String, Channel> channelMap = SSGlobalConstMap.getChannelMap();

    @Autowired
    WebSocketProcessor socketProcessor;

     /**
      * 客户端与服务端创建连接时调用
      */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {

        String longId = ctx.channel().id().asLongText();

        log.info("连接客户端地址： " + ctx.channel().remoteAddress());
        log.info("连接id ： " + ctx.channel().id() + ", LongText:" + longId);
        log.info("通知/链接已创建");

        SSGlobalConstMap.channelGroup.add(ctx.channel());
        channelMap.put(longId, ctx.channel());

//
//        System.out.println("redisC : " + wsMessageService);
//        WSMessage msg = WSMessage.builder().msgId(555L).uId(1L).msg("How Are You").msgType(1).createAt(new Date()).build();
//        wsMessageService.setWsMsg(msg);
//        System.out.println( "key : msg_" + msg.getMsgId());
//        System.out.println( "key :" + wsMessageService.getWsMeg(msg.getMsgId()));

    }

    /**
     * 断开时调用
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        String longId = ctx.channel().id().asLongText();
        channelMap.remove(longId);
        ctx.channel().close();
        channelMap.put(longId, ctx.channel());
        SSGlobalConstMap.channelGroup.add(ctx.channel());

        log.info("用户 " + ctx.channel().id() + " 链接关闭, 开始... 当前连接数:" + channelMap.size());
        log.info("链接关闭");
    }

    /**
     * 服务端接收客户端发送的数据结束后调用
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
        log.info("发送结束, 清空ctx");
    }

    /**
     * 异常
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
        String longId = ctx.channel().id().asLongText();
        channelMap.remove(longId);
        log.error("异常，连接关闭");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        log.info("channelRead开始解析请求");
        // http 请求 第一次握手请求消息由HTTP协议承载，所以它是一个HTTP消息，执行handleHttpRequest方法来处理WebSocket握手请求
        if(msg instanceof FullHttpRequest ){
            handHttpRequest(ctx, (FullHttpRequest) msg);
        }

        //WebSocket请求  客户端通过文本框提交请求消息给服务端，WebSocketServerHandler接收到的是已经解码后的WebSocketFrame消息
        if (msg instanceof WebSocketFrame){
            handWebSocketFrame(ctx, (WebSocketFrame) msg);
        }
    }


    /**
     * 核心业务处理
     * 读取对应的序列化信息，根据类型进行判断
     * extends channelRead()
     */
    @Override
    protected void messageReceived(ChannelHandlerContext ctx, Object o) throws Exception {
        log.info("messageReceived开始解析请求");
        // http 请求 第一次握手请求消息由HTTP协议承载，所以它是一个HTTP消息，执行handleHttpRequest方法来处理WebSocket握手请求
        if(o instanceof FullHttpRequest ){
            handHttpRequest(ctx, (FullHttpRequest) o);
        }

        //WebSocket请求  客户端通过文本框提交请求消息给服务端，WebSocketServerHandler接收到的是已经解码后的WebSocketFrame消息
        if (o instanceof WebSocketFrame){
            handWebSocketFrame(ctx, (WebSocketFrame) o);
        }

    }

    /**
      * 处理http请求，如果是握手请求则发起握手，如果非握手请求则返回错误请求错误信息并关闭
      */
    private void handHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req){
        log.info("开始解析http请求");
        // 是否握手请求
        if(!req.decoderResult().isSuccess()  || !(WEBSOCKET_UPGRADE.equals(req.headers().get(WEBSOCKET_CONNECTION)))){
            FullHttpResponse res = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST);
            if (res.status().code() != 200) {
                ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8);
                res.content().writeBytes(buf);
                buf.release();
                setContentLength(res, res.content().readableBytes());
            }
            // 发送请求信息
            ChannelFuture future = ctx.channel().writeAndFlush(res);
            // 是否keep-Alive
            if (!isKeepAlive(req) || res.status().code()!= 200){
                future.addListener(ChannelFutureListener.CLOSE);
            }
            log.info("解析异常");
            return;
        }

        // 获取通道地址
        InetSocketAddress inetSocket = (InetSocketAddress) ctx.channel().remoteAddress();
        String wsUrl = String.format(WEBSOCKET_URI_ROOT_PATTERN ,inetSocket.getHostName(),inetSocket.getPort());

        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(wsUrl, null, false);
        handshaker = wsFactory.newHandshaker(req);

        if (handshaker != null){
            // 通过它构造握手响应消息返回给客户端，
            // 同时将WebSocket相关的编码和解码类动态添加到ChannelPipeline中，用于WebSocket消息的编解码，
            // 添加WebSocketEncoder和WebSocketDecoder之后，服务端就可以自动对WebSocket消息进行编解码了
            handshaker.handshake(ctx.channel(), req);
        } else {
            // 发送不支持响应信息
            sendUnsupportedVersionResponse(ctx.channel());
        }

    }

     /**
      * 处理WebSocketFrame请求
      */
    private void handWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame){
        log.info("开始解析WebSocket请求");
        String longId = ctx.channel().id().asLongText();

        // 关闭请求
        if(frame instanceof CloseWebSocketFrame){
            if (handshaker == null ){
                sendErrorMsg(ctx,"错误链接 or 关闭请求");
                return;
            }
            handshaker.close(ctx.channel(), (CloseWebSocketFrame)frame.retain());
            return;
        }

        // Ping请求
        if (frame instanceof PingWebSocketFrame){
            ctx.channel().write(new PingWebSocketFrame(frame.content().retain()));
            return;
        }

        // 非文本消息
        if (!(frame instanceof TextWebSocketFrame)){
            sendErrorMsg(ctx, "非文本消息");
            return;
        }else {

            socketProcessor.handler(ctx, channelMap, (TextWebSocketFrame) frame);

        }


    }

     /**
      * 发送错误请求信息
      */
    private void sendErrorMsg(ChannelHandlerContext ctx, String msg) {
        log.info("发送错误请求信息");
         ctx.channel().writeAndFlush(new TextWebSocketFrame(msg));
    }



}
